//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Research.DataLayer;
using System.Reflection;
using System.Collections.ObjectModel;

namespace ConnectionManagerEditor
{
    public class ConfigEditor
    {
        string[] args;
        System.Type agents;
        ConnectionManagerCreator creator;

        public delegate ConnectionManagerBase ConnectionManagerCreator(string configFile, object agent,
            ConnectionManagerBase.ConfigScope scope);

        public ConfigEditor(string[] args, System.Type agents, ConnectionManagerCreator creator)
        {
            this.args = args;
            this.agents = agents;
            this.creator = creator;
        }

        enum Action { None, List, Create, Merge, Delete, Manage, Clear, SetDefault, ClearDefault, AddSQLTrusted, AddSQLUserPass, TestConn, TestConnALL };

        void CheckAndSetAction(ref Action curAction, Action newAction)
        {
            if (curAction != Action.None)
            {
                throw new ArgumentException("FATAL: Only one action option allowed");
            }

            curAction = newAction;
        }

        public int Run()
        {
            // Parse parameters
            Action action = Action.None;
            string name = null;
            string user = null;
            string pass = null;
            string host = null;
            string database = null;
            string configFile = null;
            object agent = null;
            Guid conn = Guid.Empty;
            int argLen = args.Length;
            bool verbose = false;
            bool defaultUserFile = false;
            bool scopeSet = false;
            ConnectionManagerBase.ConfigScope scope = ConnectionManagerBase.ConfigScope.Normal;

            try
            {
                for (int i = 0; i < argLen; i++)
                {
                    if (args[i].ToLowerInvariant().Equals("-user"))
                    {
                        defaultUserFile = true;
                    }
                    else if (args[i].ToLowerInvariant().Equals("-file"))
                    {
                        if (i >= argLen - 1 || args[i + 1].ToLowerInvariant().StartsWith("-"))
                            throw new ArgumentException("FATAL: Parameter '-file' requires a following filename");

                        configFile = args[i + 1];
                        i++;
                    }
                    else if (args[i].ToLowerInvariant().Equals("-agent"))
                    {
                        if (i >= argLen - 1 || args[i + 1].ToLowerInvariant().StartsWith("-"))
                            throw new ArgumentException("FATAL: Parameter '-agent' requires an agent name");

                        string agentString = args[i + 1];

                        try
                        {
                            // Check if the agent is a valid one
                            agent = Enum.Parse(agents, agentString, true);
                        }
                        catch (ArgumentException)
                        {
                            throw new ArgumentException(string.Format("FATAL: Agent '{0}' is invalid", agentString));
                        }

                        i++;
                    }
                    else if (args[i].ToLowerInvariant().Equals("-create"))
                    {
                        CheckAndSetAction(ref action, Action.Create);
                    }
                    else if (args[i].ToLowerInvariant().Equals("-delete"))
                    {
                        CheckAndSetAction(ref action, Action.Delete);
                    }
                    else if (args[i].ToLowerInvariant().Equals("-manage"))
                    {
                        CheckAndSetAction(ref action, Action.Manage);
                    }
                    else if (args[i].ToLowerInvariant().Equals("-list"))
                    {
                        CheckAndSetAction(ref action, Action.List);
                    }
                    else if (args[i].ToLowerInvariant().Equals("-clear"))
                    {
                        CheckAndSetAction(ref action, Action.Clear);
                    }
                    else if (args[i].ToLowerInvariant().Equals("-default"))
                    {
                        if (i >= argLen - 1 || args[i + 1].ToLowerInvariant().StartsWith("-"))
                            throw new ArgumentException("FATAL: Parameter '-default' requires a following connection id");

                        i++;

                        if (args[i].ToLowerInvariant().Equals("none"))
                        {
                            CheckAndSetAction(ref action, Action.ClearDefault);
                        }
                        else
                        {
                            CheckAndSetAction(ref action, Action.SetDefault);

                            try
                            {
                                conn = new Guid(args[i]);
                            }
                            catch (FormatException)
                            {
                                throw new ArgumentException("FATAL: Parameter '-default' requires a connection GUID to operate");
                            }
                        }
                    }
                    else if (args[i].ToLowerInvariant().Equals("-add"))
                    {
                        if (i >= argLen - 1)
                            throw new ArgumentException("FATAL: Parameter '-add' requires more information");

                        i++;

                        if (args[i].ToLowerInvariant().Equals("sqltrusted"))
                        {
                            if (i >= argLen - 3)
                                throw new ArgumentException("FATAL: Parameter 'sqltrusted' requires more information");

                            CheckAndSetAction(ref action, Action.AddSQLTrusted);
                            name = args[i + 1];
                            host = args[i + 2];
                            database = args[i + 3];
                            i += 3;
                        }
                        else if (args[i].ToLowerInvariant().Equals("sqluserpass"))
                        {
                            if (i >= argLen - 5)
                                throw new ArgumentException("FATAL: Parameter 'sqluserpass' requires more information");

                            CheckAndSetAction(ref action, Action.AddSQLUserPass);
                            name = args[i + 1];
                            host = args[i + 2];
                            database = args[i + 3];
                            user = args[i + 4];
                            pass = args[i + 5];
                            i += 5;
                        }
                        else
                        {
                            throw new ArgumentException(string.Format("FATAL: Unknown add type '{0}'", args[i]));
                        }
                    }
                    else if (args[i].ToLowerInvariant().Equals("-test"))
                    {
                        if (i >= argLen - 1)
                            throw new ArgumentException("FATAL: Parameter '-test' requires more information");

                        i++;

                        if (args[i].ToLowerInvariant().Equals("all"))
                        {
                            CheckAndSetAction(ref action, Action.TestConnALL);
                        }
                        else
                        {
                            CheckAndSetAction(ref action, Action.TestConn);

                            try
                            {
                                conn = new Guid(args[i]);
                            }
                            catch (FormatException)
                            {
                                throw new ArgumentException("FATAL: Parameter '-test' requires a connection GUID to operate");
                            }
                        }
                    }
                    else if (args[i].ToLowerInvariant().Equals("-scope"))
                    {
                        if (i >= argLen - 1)
                            throw new ArgumentException("FATAL: Parameter '-scope' requires more information");

                        if (scopeSet)
                            throw new ArgumentException("FATAL: Parameter '-scope' can only be set once");

                        scopeSet = true;

                        i++;

                        try
                        {
                            scope = (ConnectionManagerBase.ConfigScope)Enum.Parse(typeof(ConnectionManagerBase.ConfigScope), args[i], true);
                        }
                        catch (FormatException)
                        {
                            throw new ArgumentException("FATAL: Parameter '-scope' is invalid.");
                        }
                    }
                    else if (args[i].ToLowerInvariant().Equals("-merge"))
                    {
                        CheckAndSetAction(ref action, Action.Merge);
                    }
                    else if (args[i].ToLowerInvariant().Equals("-verbose"))
                    {
                        verbose = true;
                    }
                    else
                    {
                        throw new ArgumentException(string.Format("FATAL: Invalid parameter '{0}'", args[i]));
                    }
                }

                if (string.IsNullOrEmpty(configFile) && agent == null && !defaultUserFile)
                    throw new ArgumentException("FATAL: Parameter '-file' or '-agent' or '-user' are a mandatory parameters");

                if (scopeSet && !defaultUserFile)
                    throw new ArgumentException("FATAL: Parameter '-scope' can only be used together with '-user'");

                if(action == Action.Merge && (!defaultUserFile || (scopeSet && scope == ConnectionManagerBase.ConfigScope.Template)))
                    throw new ArgumentException("FATAL: Action '-merge' can only be used together with '-user' (and optionally '-scope normal')");

                if (action == Action.None)
                    throw new ArgumentException("FATAL: Need to specify an action");

                ConnectionManagerBase mgr;

                mgr = creator(configFile, agent, scope);

                if (verbose)
                {
                    Console.WriteLine();
                    Console.WriteLine("INFO: Storage path: {0}", mgr.ConfigDisplayName);
                    Console.WriteLine();
                }

                switch (action)
                {
                    case Action.None:
                        throw new ArgumentException("FATAL: Need to specify an action");
                    case Action.Create:
                        mgr.CreateEmptyStore();
                        Console.WriteLine("Storage initialized successfully");
                        break;
                    case Action.Delete:
                        mgr.DeleteStore();
                        Console.WriteLine("Storage deleted successfully");
                        break;
                    case Action.Manage:
                        mgr.ManageConnections(null, false);
                        break;
                    case Action.Merge:
                        mgr.MergeWithTemplate();
                        break;
                    case Action.List:
                        ListConnections(mgr);
                        break;
                    case Action.Clear:
                        mgr.ClearConnections();
                        Console.WriteLine("All connection information cleared");
                        break;
                    case Action.ClearDefault:
                        mgr.DefaultConnection = null;
                        Console.WriteLine("Default connection removed successfully");
                        break;
                    case Action.SetDefault:
                        {
                            try
                            {
                                IConnMgrInfoParams savedConn = mgr.GetConnectionByID(conn);
                                mgr.DefaultConnection = savedConn;
                                Console.WriteLine("Connection set to default successfully");
                            }
                            catch (ConnectionNotFoundException)
                            {
                                throw new ArgumentException(string.Format("FATAL: Connection '{0}' is not found in the store", conn));
                            }
                            break;
                        }
                    case Action.AddSQLTrusted:
                        {
                            //
                            // Creates a Trusted SQL Connection
                            //
                            IConnMgrInfoParams sqlParams = SQLConnectionParameters.CreateTrustedConnection(name, host, database);
                            mgr.AddConnection(sqlParams);
                            break;
                        }
                    case Action.AddSQLUserPass:
                        {
                            //
                            // Creates a User/Pass SQL Connection
                            //
                            IConnMgrInfoParams sqlParams = SQLConnectionParameters.CreateUserPassConnection(name, user, pass, host, database);
                            mgr.AddConnection(sqlParams);
                            break;
                        }
                    case Action.TestConn:
                        TestConnection(mgr, conn);
                        break;
                    case Action.TestConnALL:
                        TestAllConnections(mgr);
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
            catch (ArgumentException e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine();
                Console.WriteLine("Usage: ConnectionManagerEditor (-user [-scope normal | template] | -file CONFIG | -agent AGENT) [-verbose]");
                Console.WriteLine("             (-create | -manage | -merge | -list | -clear | -delete | -add ADDPARAM | ");
                Console.WriteLine("              -default <id> | -default NONE | -test <id> | -test ALL)");
                Console.WriteLine();
                Console.WriteLine("Parameter sets:");

                if (agents != null)
                {
                    Console.Write("          AGENT: ");
                    Array agentStrings = Enum.GetValues(agents);
                    for (int i = 0; i < agentStrings.GetLength(0); i++)
                    {
                        Console.Write(agentStrings.GetValue(i).ToString().ToLower());
                        if (i < agentStrings.GetLength(0) - 1)
                            Console.Write(" | ");
                    }
                    Console.WriteLine();
                }

                Console.WriteLine("          ADDPARAM: sqltrusted <name> <host> <database>");
                Console.WriteLine("          ADDPARAM: sqluserpass <name> <host> <database> <user> <password>");
                Console.WriteLine();
                Console.WriteLine("Examples:");
                Console.WriteLine("          ConnectionManagerEditor -file myconfig.configSources -manage");
                Console.WriteLine("          ConnectionManagerEditor -file myconfig.configSources -list");
                Console.WriteLine("          ConnectionManagerEditor -file myconfig.configSources -clear");
                Console.WriteLine("          ConnectionManagerEditor -file myconfig.configSources");
                Console.WriteLine("                    -add sqltrusted \"My Connection\" LOCALHOST\\SQLEXPRESS Trident_Registry");
                Console.WriteLine("          ConnectionManagerEditor -file myconfig.configSources");
                Console.WriteLine("                    -add sqluserpass \"My Connection\" LOCALHOST\\SQLEXPRESS Trident_Registry user **********");
                Console.WriteLine("          ConnectionManagerEditor -file myconfig.configSources -test ALL");
                Console.WriteLine("          ConnectionManagerEditor -file myconfig.configSources -test {7d6291f7-c8a0-41cb-ae34-c046c4cd3fae}");
                Console.WriteLine();
                return 1;
            }

            return 0;
        }

        void ListConnections(ConnectionManagerBase mgr)
        {
            IConnMgrInfoParams defaultConn = mgr.DefaultConnection;

            foreach (ProviderPkgInfo prov in mgr.Providers)
            {
                Console.WriteLine("--------------------------------------------------------------------------------");
                Console.WriteLine("Provider: {0} [{1}]", prov.ProviderName, prov.ProviderKey);

                if (prov.Presets == null || prov.Presets.Count == 0)
                {
                    Console.WriteLine("\tNO CONNECTIONS OF THIS KIND");
                }
                else
                {
                    foreach (IConnMgrInfoParams preset in prov.Presets)
                    {
                        Console.WriteLine("\tConnection: {0} [{1}]{2}", preset.Name, preset.ID,
                            (preset == defaultConn ? " *DEFAULT*" : ""));
                    }
                }

                Console.WriteLine();
            }
        }

        void TestConnection(ConnectionManagerBase mgr, Guid conn)
        {
            // Find the connection based on its GUID
            foreach (ProviderPkgInfo prov in mgr.Providers)
            {
                if (prov.Presets != null && prov.Presets.Count > 0)
                {
                    foreach (IConnMgrInfoParams preset in prov.Presets)
                    {
                        if (preset.ID.Equals(conn))
                        {
                            TestAndPrintResults(mgr, preset);
                            return;
                        }
                    }
                }
            }

            throw new ArgumentException(string.Format("FATAL: Connection '{0}' does not exist in the store", conn));
        }

        void TestAllConnections(ConnectionManagerBase mgr)
        {
            // Find the connection based on its GUID
            foreach (ProviderPkgInfo prov in mgr.Providers)
            {
                if (prov.Presets != null && prov.Presets.Count > 0)
                {
                    foreach (IConnMgrInfoParams preset in prov.Presets)
                    {
                        TestAndPrintResults(mgr, preset);
                    }
                }
            }
        }

        void TestAndPrintResults(ConnectionManagerBase mgr, IConnMgrInfoParams preset)
        {
            BackendStorageException error;
            bool success = mgr.TestConnection(preset, out error);

            Console.WriteLine("--------------------------------------------------------------------------------");
            Console.WriteLine("Connection Test: {0} [{1}] : {2}", preset.Name, preset.ID,
                success ? "SUCCESS" : "FAILED");
            if (!success)
            {
                Console.WriteLine("\tError: {0}", error.Message.Replace("\n", "\n\t\t"));
                if (error.InnerException != null)
                    Console.WriteLine("\tError: {0}", error.InnerException.Message.Replace("\n", "\n\t\t"));
            }
        }
    }

    class Program
    {
        [STAThread]
        static int Main(string[] args)
        {
            ConfigEditor cfg = new ConfigEditor(args, null,
                new ConfigEditor.ConnectionManagerCreator(delegate(string file, object agent, ConnectionManagerBase.ConfigScope scope)
                {
                    return BasicConnectionManager.Create(BasicConnectionManager.CancelBehavior.SilentlyReturnNull, file);
                }));
            return cfg.Run();
        }
    }
}
